﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Web;
using BSF.Api;
using BSF.Db;
using BSF.Enums;
using BSF.Log;

namespace BSF.Tool
{
    /// <summary>
    /// sign验证辅助类
    /// </summary>
    /// <author>沈鑫锋</author>
    public class SignHelper
    {
        #region 参数
        /// <summary>
        /// 第一个分隔符
        /// </summary>
        private static readonly string splitCharOne = ":";

        /// <summary>
        /// 第二个分隔符
        /// </summary>
        private static readonly string splitCharTwo = ",";

        /// <summary>
        /// 允许的时间差 单位分钟
        /// </summary>
        //  public  int intervalTime = 6;
        #endregion

        /// <summary>
        /// 验证sign 默认Capi端
        /// </summary>
        /// <param name="request"></param>
        /// <param name="intervalTime"></param>
        /// <param name="err"></param>
        /// <returns></returns>
        public static bool ValidateSign(HttpRequest request, int intervalTime, out string err)
        {
            return ValidateSign(request, intervalTime, out  err, UserTypeEnum.sns_default);
        }

        /// <summary>
        /// 验证sign
        /// </summary>
        /// <remarks>
        /// 签名规则：
        /// 1.对所有待签名参数按照字段名的ASCII 码从小到大排序（字典序）,排除sign
        /// 2.使用URL键值对的格式（即key1:value1,key2:value2…）拼接成字符串string1
        /// 3.对string1作sha1加密，字段名和字段值都采用原始值，不进行URL 转义,sha1加密工具:http://tool.oschina.net/encrypt?type=2
        /// 参考:微信JS SDK使用权限签名算法 http://www.cnblogs.com/txw1958/p/weixin-jsapi-signature.html
        /// http://mp.weixin.qq.com/wiki/4/2ccadaef44fe1e4b0322355c2312bfa8.html?ptlang=2052&ADUIN=346510706&ADSESSION=1473384359&ADTAG=CLIENT.QQ.5473_.0&ADPUBNO=26569
        /// </remarks>
        /// <param name="request"></param>
        /// <param name="intervalTime"></param>
        /// <param name="err"></param>
        /// <param name="serviceType"></param>
        /// <returns></returns>
        private static bool ValidateSign(HttpRequest request, int intervalTime, out string err, UserTypeEnum serviceType)
        {
            err = "";
            bool isTest = false;
            string sign = request.Params["sign"];//签名
            long timestamp = LibConvert.StrToInt64(request["timestamp"]);//时间戳
            string[] keys = request.Form.AllKeys;
            if (!keys.Any())
            {
                keys = request.QueryString.AllKeys;
            }
            //if (sign == "mySign")
            //{
            //    return true;
            //}

            if (string.IsNullOrWhiteSpace(sign))
            {
                err = "缺少签名sign";
                return false;
            }

            if (isTest)
            {
                CommLog.Write("请求地址:" + request.Url + "\r\n签名时间:" + timestamp + " 我的时间:" + CommonHelper.GetTimeStamp(DateTime.Now.AddMinutes(-intervalTime)));

            }
            if (CommonHelper.GetTimeStamp(DateTime.Now.AddMinutes(-intervalTime)) > timestamp)
            {
                err = "请求时间超时";
                return false;
            }

            List<SignModel> signModels = new List<SignModel>();
            //TODO 对appid进行校验,防止模拟器调用
            ////增加隐藏签名参数 appSecret
            //string appid = request.Form["appid"];
            ////XXF.BasicService.CertCenter.CertCenterProvider ccp = new XXF.BasicService.CertCenter.CertCenterProvider(serviceType);
            ////string appSecret = ccp.GetAppSecret(appid);//通过appid获取appsecret
            //signModels.Add(new SignModel
            //{
            //    key = "appsecret",
            //    value = appSecret
            //    //"f5ed16cfa6e842a1aa1322ad04a2c99a"
            //    //asc = GetAsc("appsecret")
            //});
            foreach (var m in keys)
            {
                if (string.IsNullOrWhiteSpace(m) || m[0] == '_' || String.Compare(m, "sign", StringComparison.OrdinalIgnoreCase) == 0)
                {
                    continue;
                }
                SignModel model = new SignModel
                {
                    key = m,
                    value = request[m]
                };
                //获取asc编码
                //model.asc = GetAsc(model.key);
                signModels.Add(model);
            }


            StringBuilder signStr = new StringBuilder();

            //按照asc码升序排列
            foreach (var m in signModels.OrderBy(m => m.key, StringComparer.Ordinal))
            {
                signStr.Append(m.key).Append(splitCharOne).Append(m.value).Append(splitCharTwo);
            }

            //去除最后一个多余的分隔符
            if (signStr.Length > 0)
            {
                signStr.Remove(signStr.Length - splitCharTwo.Length, splitCharTwo.Length);
            }

            //string mySign = LibCrypto.En32MD5(signStr.ToString());//md5加密
            string mySign = CommonHelper.EncryptSHA1(signStr.ToString());//SHA1加密
            if (isTest)
            {
                CommLog.Write("签名参数" + signStr.ToString() + "签名sign:" + mySign);
            }
            if (sign.CompareTo(mySign) != 0)
            {
                //  err = sign + "服务器检测到非法调用" + mySign;
                err = "服务器检测到非法调用";
                if (isTest)
                {
                    CommLog.Write("签名失败");
                }
                CommLog.Write("服务器检测到非法调用" + request.Params.ToString() + " " + signStr);
                return false;
            }
            if (isTest)
            {
                CommLog.Write("签名成功");
            }
            return true;
        }

        /// <summary>
        /// 生成签名
        /// </summary>
        /// <param name="parms">参数列表</param>
        /// <param name="appsecret">密钥</param>
        /// <returns></returns>
        public static string CreateSign(List<ParmField> parms, string appsecret)
        {
            List<SignModel> signModels = new List<SignModel>();
            foreach (var m in parms)
            {
                if (!string.IsNullOrWhiteSpace(m.Key) && m.Key[0] != '_' && m.Key.CompareTo("snssign") != 0)//以下划线开头的参数和sign不参与sign签名  
                {
                    SignModel model = new SignModel();
                    model.key = m.Key;
                    model.value = m.Value;
                    signModels.Add(model);
                }
            }
            signModels.Add(new SignModel { key = "appsecret", value = appsecret });

            StringBuilder signStr = new StringBuilder();
            //按照asc码升序排列
            foreach (var m in signModels.OrderBy(m => m.key, StringComparer.Ordinal))
            {
                signStr.Append(m.key).Append(splitCharOne).Append(m.value).Append(splitCharTwo);
            }

            //去除最后一个多余的分隔符
            if (signStr.Length > 0)
            {
                signStr.Remove(signStr.Length - splitCharTwo.Length, splitCharTwo.Length);
            }

            string mySign = LibCrypto.En32MD5(signStr.ToString());
            return mySign;
        }


        struct SignModel
        {
            /// <summary>
            /// key值
            /// </summary>
            public string key { get; set; }

            /// <summary>
            /// 值
            /// </summary>
            public string value { get; set; }

            /// <summary>
            /// asc码
            /// </summary>
            //public int asc { get; set; }
        }

        /// <summary>
        /// 获取字符串的asc编码
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        private static int GetAsc(string value)
        {
            //int asc = 0;
            //foreach (var ch in value)
            //{
            //    asc += Convert.ToInt32(ch);
            //}
            //return asc;
            return value.Sum(ch => Convert.ToInt32(ch));
        }

        //private bool ComparerByASCII:IComparer<string>
        //{
        //}

        #region 电子签章(Electronic Signature)

        /**/
        /// <summary>
        /// 将Byte[]转换成十六进制字符串
        /// </summary>
        /// <param name="bytes">要转换的Byte[]</param>
        /// <returns>十六进制字符串</returns>
        public static string ConvertBytesToString(byte[] bytes)
        {
            string bytestring = string.Empty;
            if (bytes != null && bytes.Length > 0)
            {
                for (int i = 0; i < bytes.Length; i++)
                {
                    bytestring += bytes[i].ToString("X") + " ";
                }
            }
            return bytestring;
        }

        /**/
        /// <summary>
        /// 得到指定电子文件的哈希
        /// </summary>
        /// <param name="filePath">电子文件地址</param>
        /// <returns>哈希值</returns>
        public static byte[] GetFileHash(string filePath)
        {
            try
            {
                FileStream objFile = File.OpenRead(filePath);
                HashAlgorithm MD5 = HashAlgorithm.Create("MD5");
                byte[] Hashbyte = MD5.ComputeHash(objFile);
                objFile.Close();
                return Hashbyte;
            }
            catch
            {
                return null;
            }
        }

        /**/
        /// <summary>
        /// 得到公钥与私钥
        /// </summary>
        /// <param name="ContainerName">私钥容器名</param>
        /// <param name="privatekey">真为得到私钥，假为得到公钥</param>
        /// <returns>公钥或私钥</returns>
        public static string GetKeyFromContainer(string ContainerName, bool privatekey)
        {
            CspParameters cp = new CspParameters();
            cp.KeyContainerName = ContainerName;
            RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(cp);
            return rsa.ToXmlString(privatekey);
        }

        /**/
        /// <summary>
        /// 对哈希进行数字签名
        /// </summary>
        /// <param name="privateKey">私钥</param>
        /// <param name="fileHash">电子文件哈希</param>
        /// <returns></returns>
        public static byte[] EncryptHash(string privateKey, byte[] fileHash)
        {
            RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();

            RSA.FromXmlString(privateKey);

            RSAPKCS1SignatureFormatter RSAFormatter = new RSAPKCS1SignatureFormatter(RSA);

            RSAFormatter.SetHashAlgorithm("MD5");

            return RSAFormatter.CreateSignature(fileHash);
        }

        /**/
        /// <summary>
        /// 对数字签名用公钥进行验证
        /// </summary>
        /// <param name="publicKey">公钥</param>
        /// <param name="fileHash">接收到的电子文件的哈希</param>
        /// <param name="electronicSignature">数字签名</param>
        /// <returns>数字签名有效为真，数字签名无效为假</returns>
        public static bool DecryptHash(string publicKey, byte[] fileHash, byte[] electronicSignature)
        {
            RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();

            RSA.FromXmlString(publicKey);

            RSAPKCS1SignatureDeformatter RSADeformatter = new RSAPKCS1SignatureDeformatter(RSA);

            RSADeformatter.SetHashAlgorithm("MD5");

            return RSADeformatter.VerifySignature(fileHash, electronicSignature);
        }

        #endregion

        #region 验证签名
        /// <summary>验证签名</summary>
        /// <summary>signature其实是一段加密过的字符，它是微信用token，timestamp,nonce这三个参数进行sha1加密后的字符串。我们的代码里，也是要对这三个参数进行sha1加密，然后和signature进行比对，一致的话，就表示可信的，然后把echostr这个随机字符串返回给微信，信任就建立了。</summary>
        /// <param name="token">Token</param>
        /// <param name="timestamp">时间戳</param>
        /// <param name="nonce">随机数</param>
        /// <returns></returns>
        public static string GetSignature(string token, string timestamp, string nonce)
        {
            #region 验证签名
            //开发者通过检验signature对网址接入合法性进行校验：
            //1.将taken,timestamp,nonce三个参数进行字典序排序
            //2.将三个参数字符串拼接成一个字符串进行sha1加密
            //3.开发者获得加密后的字符串可与signature对比，标识该请求来源于微信
            //开发者通过检验signature对请求进行校验（下面有校验方式）。若确认此次GET请求来自微信服务器，请原样返回echostr参数内容，则接入生效，成为开发者成功，否则接入失败。 
            //SortedDictionary<string, string> parmlist = new SortedDictionary<string, string>();
            //parmlist.Add(timestamp, timestamp);
            //parmlist.Add(nonce, nonce);
            //parmlist.Add(token, token);
            //string parmstring = "";
            //foreach (KeyValuePair<string, string> kvp in parmlist)
            //{
            //    parmstring = parmstring + kvp.Value;
            //}
            //return EncryptSHA1(parmstring);

            Dictionary<string, string> parmlist = new Dictionary<string, string>();//实例化一个字典
            parmlist.Add(timestamp, timestamp);
            parmlist.Add(nonce, nonce);
            parmlist.Add(token, token);
            List<KeyValuePair<string, string>> lstorder = parmlist.OrderBy(c => c.Key).ToList();
            string parmstring = "";
            foreach (KeyValuePair<string, string> kvp in lstorder)
            {
                parmstring = parmstring + kvp.Value;
            }
            return CommonHelper.EncryptSHA1(parmstring);
            #endregion
        }

        /// <summary>验证签名</summary>
        /// <param name="token">Token</param>
        /// <param name="timestamp">时间戳</param>
        /// <param name="nonce">随机数</param>
        /// <param name="signature">标准签名</param>
        /// <returns></returns>
        public static bool CheckSignature(string token, string timestamp, string nonce, string signature)
        {
            if (string.IsNullOrEmpty(signature)) return false;
            string checksignature = GetSignature(token, timestamp, nonce);
            return signature.ToLower() == checksignature.ToLower();
            //return signature.Equals(checksignature, StringComparison.InvariantCultureIgnoreCase);

            #region 验证签名
            ////string[] ArrTmp = { token, timestamp, nonce };
            ////Array.Sort(ArrTmp);//字典排序
            ////string tmpStr = string.Join("", ArrTmp);
            //List<string> tmpList = new List<string>(3);
            //tmpList.Add(token);
            //tmpList.Add(timestamp);
            //tmpList.Add(nonce);
            //tmpList.Sort();
            //var tmpStr = string.Join("", tmpList.ToArray());
            //tmpStr = System.Web.Security.FormsAuthentication.HashPasswordForStoringInConfigFile(tmpStr, "SHA1");
            //tmpStr = tmpStr.ToLower();
            //return signature.Equals(tmpStr, StringComparison.InvariantCultureIgnoreCase);
            #endregion
        }

        /// <summary>
        /// 获取分享数据包 分享页面使用  
        /// </summary>
        /// <returns></returns>
        public static System.Collections.Hashtable getSignPackage(string appId, string ticket)
        {
            string url = HttpContext.Current.Request.Url.ToString();
            string timestamp = CommonHelper.UnixInter().ToString();
            string nonceStr = CommonHelper.RandomString();
            // 这里参数的顺序要按照 key 值 ASCII 码升序排序  
            string rawstring = "jsapi_ticket=" + ticket + "&noncestr=" + nonceStr + "&timestamp=" + timestamp + "&url=" + url + "";
            string signature = CommonHelper.SHA1_Hash(rawstring);

            System.Collections.Hashtable signPackage = new System.Collections.Hashtable();
            signPackage.Add("appId", appId);
            signPackage.Add("nonceStr", nonceStr);
            signPackage.Add("timestamp", timestamp);
            signPackage.Add("url", url);
            signPackage.Add("signature", signature);
            signPackage.Add("rawString", rawstring);
            return signPackage;
        }

        /// <summary>
        /// 获取签名
        /// </summary>
        /// <param name="accessToken">凭证</param>
        /// <returns></returns>
        public static string GetSignture(string accessToken, ref string timestamp, ref string nonceStr, string jsticket = "", string url = "")
        {
            if (string.IsNullOrWhiteSpace(url))
                url = HttpContext.Current.Request.Url.ToString();
            timestamp = CommonHelper.UnixInter().ToString();
            nonceStr = CommonHelper.RandomString();

            // 这里参数的顺序要按照 key 值 ASCII 码升序排序  
            string rawstring = "jsapi_ticket=" + jsticket + "&noncestr=" + nonceStr + "&timestamp=" + timestamp + "&url=" + url + "";

            string signature = CommonHelper.SHA1_Hash(rawstring);

            return signature;
        }

        #endregion
    }
}
